import { ByteView } from "./codec/byte_view";
import { CodecBase, u16, u32, u8 } from "./codec/codec_type";
import device from "./device/device"
import { TimeoutErr } from "./error";
import { makeProto, Type, Cmd } from "./proto";
import { RecvFrame, registerResult } from "./recv";
import { md5Sum } from "./utils";

let globalSeq: number = 0;

function createSeq() {
    if (globalSeq > 255) {
        globalSeq = 0;
    }
    const v = globalSeq;
    globalSeq += 1;
    return v;
}

function createPromise(type: number, seq: number, timeout: number = 500) {
    const key = (type << 8) + seq;
    return new Promise<RecvFrame>((resolve, reject) => {
        const timerId = setTimeout(() => {
            reject(new TimeoutErr('返回超时'));
        }, timeout);
        registerResult(key, (frame) => {
            clearTimeout(timerId);
            resolve(frame);
        });
    });
}

async function ack(seq: number) {
    const buf = makeProto(Type.ACK, seq);
    await device.serialPortWrite(buf);
}

async function ping() {
    const seq = createSeq();
    const buf = makeProto(Type.PING, seq);
    const pong = createPromise(Type.PONG, seq);
    await device.serialPortWrite(buf);
    await pong;
}

async function simpleReq(cmd: number, args: CodecBase[], result: CodecBase[], timeout = 500) {
    const seq = createSeq();
    const buf = makeProto(Type.SIMPLE_REQ, seq, u8(cmd), ...args);
    const simpleRes = createPromise(Type.SIMPLE_RES, seq, timeout);
    await device.serialPortWrite(buf);
    const frame = await simpleRes;
    frame.parse_res(...result);
}

async function try_session() {
    const seq = createSeq();
    const buf = makeProto(Type.SESSION, seq);
    const ack = createPromise(Type.ACK, seq);
    await device.serialPortWrite(buf);
    await ack;
}

async function session() {
    for (let i = 0; i < 3; i++) {
        try {
            await try_session();
            return;
        } catch (e) {
            if (!(e instanceof TimeoutErr)) {
                throw e;
            }
        }
    }
    throw `session失败`;
}

async function write_req(buf: number[], seq: number) {
    for (let i = 0; i < 3; i++) {
        const ack = createPromise(Type.ACK, seq);
        await device.serialPortWrite(buf);
        try {
            await ack;
            return;
        } catch (e) {
        }
    }
    throw `write req 失败`;
}

async function check_task(seq: number, cmd: number) {
    const result_seq = u8();
    const result_cmd = u8();
    await simpleReq(Cmd.GET_TASK_INFO, [], [result_seq, result_cmd]);
    return (seq == result_seq.value) && (cmd == result_cmd.value);
}

async function req(cmd: number, args: CodecBase[], result: CodecBase[]) {
    await session();
    const seq = createSeq();
    const buf = makeProto(Type.REQ, seq, u8(cmd), ...args);
    let res = createPromise(Type.RES, seq, 3000);
    await write_req(buf, seq);
    for (; ;) {
        try {
            const frame = await res;
            await ack(frame.seq());
            frame.parse_res(...result);
            return;
        } catch (e) {
            if (!(e instanceof TimeoutErr)) {
                throw e;
            }
            console.log('req 执行超时');
            res = createPromise(Type.RES, seq, 3000);
            if (await check_task(seq, cmd)) {
                console.log('执行中');
                continue;
            }
            console.log('执行结束');
            const frame = await res;
            await ack(frame.seq());
            frame.parse_res(...result);
            return;
        }
    }
}

export default {

    async ping() {
        await ping();
    },

    async nozzleCtrl(mm: number) {
        await req(Cmd.NOZZLE_CTRL, [u16(mm)], []);
    },

    async doorOpen() {
        await req(Cmd.DOOR_CTRL, [], []);
    },

    async scan() {
        const bw = new ByteView([], 0, 0);
        await req(Cmd.SCAN, [], [bw]);
        return bw.toString();
    },

    async set_param(key: number, value: number) {
        await req(Cmd.PARAM, [u8(key), u16(value)], []);
    },

    async get_param(key: number) {
        const v = u16();
        await simpleReq(Cmd.PARAM, [u8(key)], [v]);
        return v.value;
    },

    async ota(buf: Uint8Array, cb: (prog: number) => void) {
        const size = buf.length;
        await simpleReq(Cmd.OTA_START, [u32(size)], [], 3000);
        let id = 0;
        for (let index = 0; index < size; ) {
            let n = size - index;
            if (n > 220) {
                n = 220;
            }
            const bw = new ByteView(buf, index, n);
            await simpleReq(Cmd.OTA_TRANSLATE, [u16(id), bw], [], 800);
            index += n;
            id += 1;
            cb( Math.floor(index * 100 / size) );
        }
        const sum = md5Sum(buf);
        await simpleReq(Cmd.OTA_COMPLETE, [new ByteView(sum, 0, sum.length)], [], 2000);
    },

    async partsCtrl(id: number, value: number) {
        await simpleReq(Cmd.PARTS_CTRL, [u8(id), u8(value)], []);
    },

    async drawHeatCtrl(ctrl: number) {
        await simpleReq(Cmd.DRAW_HEAT_CTRL, [u8(ctrl)], []);
    },
    
    async drawWater(mm: number, temp: number, ml: number) {
        await req(Cmd.DRAW_WATER_CTRL, [u16(mm), u8(temp), u16(ml)], []);
    }
}
